package cn.seaboot.flake.core;

import cn.seaboot.commons.core.Converter;
import cn.seaboot.commons.lang.Warning;
import cn.seaboot.commons.reflect.FieldAccess;
import cn.seaboot.flake.mapping.ParameterMap;
import cn.seaboot.flake.mapping.ParameterMapping;
import cn.seaboot.flake.mapping.ResultMap;
import cn.seaboot.flake.call.FlackCallableStatementCallback;
import cn.seaboot.flake.call.FlackCallableStatementCreator;
import cn.seaboot.flake.call.FlackResultSetExtractor;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.*;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Flack
 * <p>
 * 这是一个未完成的工具类，需要进一步封装进行使用。
 * 需要与业务逻辑相结合，形成一套完整的逻辑。
 * <p>
 * 这个类封装了很多核心的代码：分词器的设计，参数的解析，和 SQL 命令的执行。
 *
 * @author Mr.css
 * @version 2022-01-27 11:17
 */
public class Flake {
    private final Logger logger = LoggerFactory.getLogger(Flake.class);
    /**
     * JDBC
     */
    private JdbcTemplate jdbcTemplate;
    /**
     * 分词器
     */
    private FlakeTokenizer tokenizer = SqlHelper.DEF_TOKENIZER;
    /**
     * 模版引擎
     */
    private FlakeTemplate flakeTemplate = SqlHelper.DEF_TEMPLATE;


    /**
     * 预编译 SQL
     * <p>
     * 这个函数是核心，负责将 SQL 模版，渲染成业务上能用的查询语句；
     * 经过处理才查询语句，可以被 <code>PreparedStatement<code/> 直接直接使用；
     * <p>
     * 这意味着，实际上可以对接大部分 JDBC 实现类。
     *
     * @param sql    语句配置
     * @param params 处理好的参数
     * @return 可供执行的SQL和参数
     */
    public PreparedSql processSql(String sql, Map<String, Object> params) {
        // 模版引擎处理基本逻辑
        String tpl = flakeTemplate.process(sql, params);
        // 去除所有占位符
        PreparedSql preparedSql = tokenizer.process(tpl, params);
        logger.debug("Flake process sql: {}", preparedSql);
        return preparedSql;
    }

    /**
     * 处理参数
     * <p>
     * 根据 ParameterMap 配置，对查询参数进行 “过滤” 和 “数据类型转换”，方便程序进一步处理。
     * <p>
     * 执行查询时，这并不是必要的一个步骤。
     *
     * @param params       参数
     * @param parameterMap 参数配置
     * @return 可供使用的参数
     */
    public Map<String, Object> processMapParameter(Map<String, Object> params, ParameterMap parameterMap) {
        Map<String, Object> ret = new HashMap<>();
        List<ParameterMapping> parameterMappings = parameterMap.getParameterMappings();
        for (ParameterMapping mapping : parameterMappings) {
            Object value = params.get(mapping.getProperty());
            if (mapping.getJavaType() != null) {
                value = Converter.convert(value, mapping.getJavaType());
            }
            ret.put(mapping.getProperty(), value);
        }
        return ret;
    }

    /**
     * 处理参数
     * <p>
     * 根据 ParameterMap 配置，对查询参数进行 “过滤” 和 “数据类型转换”，方便程序进一步处理。
     * <p>
     * 执行查询时，这并不是必要的一个步骤。
     *
     * @param params       参数
     * @param parameterMap 参数配置
     * @return 可供使用的参数
     */
    @SuppressWarnings(Warning.UNCHECKED)
    public Map<String, Object> processParameter(Object params, ParameterMap parameterMap) {
        if (params instanceof Map) {
            return this.processMapParameter((Map<String, Object>) params, parameterMap);
        } else {
            Map<String, Object> ret = new HashMap<>();
            List<ParameterMapping> parameterMappings = parameterMap.getParameterMappings();
            FieldAccess fieldAccess = FieldAccess.create(parameterMap.getClass());
            for (ParameterMapping mapping : parameterMappings) {
                Object value = fieldAccess.getValue(params, mapping.getProperty());
                if (mapping.getJavaType() != null) {
                    value = Converter.convert(value, mapping.getJavaType());
                }
                ret.put(mapping.getProperty(), value);
            }
            return ret;
        }
    }

    /**
     * 执行更新
     *
     * @param preparedSql sql and values
     * @return affected rows
     */
    public int update(PreparedSql preparedSql) {
        return jdbcTemplate.update(preparedSql.getSql(), preparedSql.getValues());
    }

    /**
     * 执行查询，使用{@link ResultMap}进行结果集转换
     *
     * @param preparedSql sql and values
     * @param resultMap   返回值类型
     * @param <T>         泛型
     * @return list
     */
    public <T> List<T> query(PreparedSql preparedSql, ResultMap resultMap) {
        ResultSetExtractor<List<T>> rse = new FlackResultSetExtractor<>(resultMap);
        return jdbcTemplate.query(preparedSql.getSql(), rse, preparedSql.getValues());
    }

    /**
     * 执行查询，使用{@link ResultMap}进行结果集转换
     *
     * @param preparedSql sql and values
     * @param resultMap   返回值类型
     * @param <T>         泛型
     * @return Object
     */
    public <T> T queryOne(PreparedSql preparedSql, ResultMap resultMap) {
        List<T> list = this.query(preparedSql, resultMap);
        if (list.size() > 1) {
            throw new TooManyResultsException("Receive too many result when execute: " + preparedSql);
        }
        return list.get(0);
    }

    /**
     * 执行查询，将结果打包成ListMap格式
     *
     * @param preparedSql sql and values
     * @return list
     */
    public List<Map<String, Object>> query(PreparedSql preparedSql) {
        return jdbcTemplate.queryForList(preparedSql.getSql(), preparedSql.getValues());
    }

    /**
     * 执行查询，将结果打包成 Map 格式
     *
     * @param preparedSql sql and values
     * @return Object
     */
    public Map<String, Object> queryOne(PreparedSql preparedSql) {
        List<Map<String, Object>> list = this.query(preparedSql);
        if (list.size() > 1) {
            throw new TooManyResultsException("Receive too many result when execute: " + preparedSql);
        }
        return list.get(0);
    }

    /**
     * 执行存储过程
     *
     * @param preparedSql  sql and values
     * @param parameterMap 参数配置，这是必须的，要声明哪些参数，是 OUT 参数
     * @param params       参数，OUT 参数运行结果也会存储在 params 中
     * @return 返回值，函数不会构造一个新的 map，直接将数据填充到 map 中
     */
    public Integer call(PreparedSql preparedSql, ParameterMap parameterMap, Map<String, Object> params) {
        return this.call(preparedSql, parameterMap, params, params);
    }

    /**
     * 执行存储过程
     *
     * @param preparedSql  sql and values
     * @param parameterMap 参数配置
     * @param params       IN 入参
     * @param result       OUT 出参
     * @return effect rows
     */
    public Integer call(PreparedSql preparedSql, ParameterMap parameterMap, Map<String, Object> params, Object result) {
        Assert.notNull(parameterMap, "ParameterMap can not be null when execute procedure");
        CallableStatementCreator csc = new FlackCallableStatementCreator(preparedSql.getSql(), parameterMap, params);
        CallableStatementCallback<Integer> action = new FlackCallableStatementCallback(parameterMap, result);
        return jdbcTemplate.execute(csc, action);
    }

    //getter/setter ----------------------------------------------------------------

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public FlakeTokenizer getTokenizer() {
        return tokenizer;
    }

    public void setTokenizer(FlakeTokenizer tokenizer) {
        this.tokenizer = tokenizer;
    }

    public FlakeTemplate getFlakeTemplate() {
        return flakeTemplate;
    }

    public void setFlakeTemplate(FlakeTemplate flakeTemplate) {
        this.flakeTemplate = flakeTemplate;
    }
}
